package org.openapi.utils;

import com.alibaba.fastjson2.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.script.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * JS脚本工具
 * 约定：
 * 1、脚本包括一个exec方法
 * 2、exec最多支持输入一个字符串做为参数
 * 3、返回值也是一个字符串，如果是对象就先转换成字符串
 */
public class JsUtil {
    private static final Logger log = LoggerFactory.getLogger(JsUtil.class);
    private static final String EXEC_NAME = "exec";
    private static final String DO_EXEC_NAME = "do_exec";
    /** 脚本引擎 **/
    public static ScriptEngine engine;

    static {
        //脚本引擎管理
        ScriptEngineManager manager = new ScriptEngineManager();
        //获取nashorn脚本引擎
        engine = manager.getEngineByName("javascript");
        //获取正文并且写入
        engine.getContext().getWriter();
    }

    /**v脚本存储容器 **/
    private static ConcurrentHashMap<Integer, CompiledScript> scripts = new ConcurrentHashMap<>();


    /**
     * 编译脚本并缓存
     * @param script
     * @return
     * @throws ScriptException
     */
    private static CompiledScript getCompiledScript(String script) throws ScriptException {
        //判断脚本是否为空
        if(script == null || "".equals(script)){
            throw new ScriptException("JavaScript empty");
        }
        //获取脚本Hash
        int hashCode = script.hashCode();
        //从容器中获取脚本
        CompiledScript compiledScript = scripts.get(hashCode);
        if(compiledScript == null){
            //容器中无脚本创建脚本对象
            Compilable compilable = (Compilable) engine;
            //编译JavaScript脚本
            compiledScript = compilable.compile(script);
            //脚本对象存入容器中
            scripts.put(hashCode, compiledScript);
        }
        return compiledScript;
    }

    /**
     * 执行JS脚本
     * @param script 脚本代码段
     * @param params 字符串参数
     */
    public static String eval(String script, String params){
        log.info("eval javascript:\r\n {} \r\n prarms: {}", script, params);
        try{
            //获取脚本对象
            CompiledScript cs = getCompiledScript(script);
            cs.eval();
            Object ret = ((Invocable)cs.getEngine()).invokeFunction(EXEC_NAME, params);
            if(ret == null){
                return null;
            }else{
                return ret.toString();
            }
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 执行JS脚本
     * @param script 脚本代码段
     * @param params 对象参数
     */
    public static String exec(String script, Object params){
        log.info("exec javascript:\r\n {} \r\n prarms: {}", script, params);
        try{
            script += "\r\nfunction do_exec(paramStr){ var param = (paramStr ? JSON.parse(paramStr) : null); var ret = exec(param); return (ret && (ret instanceof Object|| ret instanceof Array)) ? JSON.stringify(ret) : ret;}";
            //获取脚本对象
            CompiledScript cs = getCompiledScript(script);
            cs.eval();
            Invocable inv = ((Invocable)cs.getEngine());
            Object ret = null;
            if(params == null){
                ret = inv.invokeFunction(DO_EXEC_NAME);
            }else if(params instanceof String){
                ret = inv.invokeFunction(DO_EXEC_NAME, params.toString());
            }else{
                ret = inv.invokeFunction(DO_EXEC_NAME, JSON.toJSONString(params));
            }
            log.info("got: {} ", ret == null ? "null" : ret.toString());
            if(ret == null){
                return null;
            }else{
                return ret.toString();
            }
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 判断脚本是否为JS动态函数
     * @return
     */
    public static Boolean isFun(String content){
        return content.contains("function ") && content.contains(" exec");
    }

    public static void main(String[] args){
        String jsStr = "function exec(caches){\n" +
                " var ret = {  \n" +
                "  uri:\"https://quhua.ipchaxun.com/api/areas/data?parentcode=100000\",  \n" +
                "  header:{},  \n" +
                "  data:{} \n" +
                " };\n if(caches){ret.caches = caches;}" +
                "return  JSON.stringify(ret);\r\n" +
                "}";
        Object v = eval(jsStr, "{age:3}");
        System.out.println(v);
        v = exec(jsStr, null);
        System.out.println(v);
    }
}
